#include <system/Timer.h>

namespace ne
{
    Timer::Timer()
    {
        reset();
    }

    double Timer::getIntervalMilliseconds()
    {
        mLastTime = mNewTime;
        mNewTime = getMilliseconds();
        return mNewTime - mLastTime;
    }

    double Timer::getIntervalMillisecondsCPU()
    {
        mLastClock = mNewClock;
        mNewClock = getMillisecondsCPU();
        return mNewClock - mLastClock;
    }

#if NE_PLATFORM == NE_PLATFORM_WINDOWS
    void Timer::reset()
    {
        // Get the current process core mask
        DWORD_PTR procMask;
        DWORD_PTR sysMask;
        GetProcessAffinityMask(GetCurrentProcess(), &procMask, &sysMask);
        // If procMask is 0, consider there is only one core available
        // (using 0 as procMask will cause an infinite loop below)
        if (procMask == 0)
        {
            procMask = 1;
        }

        // Find the lowest core that this process uses
        if (mMask == 0)
        {
            mMask = 1;
            while ((mMask & procMask) == 0)
            {
                mMask <<= 1;
            }
        }

        HANDLE thread = GetCurrentThread();
        // Set affinity to the first core
        DWORD_PTR oldMask = SetThreadAffinityMask(thread, mMask);
        // Get the constant frequency
        QueryPerformanceFrequency(&mFrequency);
        // Query the timer
        QueryPerformanceCounter(&mZeroTime);
        mStartTick = GetTickCount();
        mLast = 0;
        // Reset affinity
        SetThreadAffinityMask(thread, oldMask);

        mLastTime = 0;
        mNewTime = 0;

        mZeroClock = clock();
        mLastClock = 0;
        mNewClock = 0;
    }

    double Timer::getSeconds()
    {
        LARGE_INTEGER now;
        HANDLE thread = GetCurrentThread();
        // Set affinity to the first core
        DWORD_PTR oldMask = SetThreadAffinityMask(thread, mMask);
        // Query the timer
        QueryPerformanceCounter(&now);
        // Reset affinity
        SetThreadAffinityMask(thread, oldMask);
        double newTime = double(now.QuadPart - mZeroTime.QuadPart);
        // Scale by 1000 for milliseconds
        double newTicks = newTime * 1000 / mFrequency.QuadPart;
        // Detect and compensate for performance counter leaps
        // (Surprisingly common, see Microsoft KB: Q274323)
        double check = double(GetTickCount() - mStartTick);
        double msecOff = newTicks - check;
        if (msecOff < -100 || msecOff > 100)
        {
            // We must keep the timer running forward :)
            LONGLONG adjust = std::min(msecOff * mFrequency.QuadPart / 1000, newTime - mLast);
            mZeroTime.QuadPart += adjust;
            newTime -= adjust;
        }

        // Record last time for adjust
        mLast = newTime;

        return newTime / mFrequency.QuadPart;
    }

    double Timer::getMilliseconds()
    {
        LARGE_INTEGER now;
        HANDLE thread = GetCurrentThread();
        // Set affinity to the first core
        DWORD_PTR oldMask = SetThreadAffinityMask(thread, mMask);
        // Query the timer
        QueryPerformanceCounter(&now);
        // Reset affinity
        SetThreadAffinityMask(thread, oldMask);
        double newTime = double(now.QuadPart - mZeroTime.QuadPart);
        // Scale by 1000 for milliseconds
        double newTicks = newTime * 1000 / mFrequency.QuadPart;
        // Detect and compensate for performance counter leaps
        // (Surprisingly common, see Microsoft KB: Q274323)
        double check = double(GetTickCount() - mStartTick);
        double msecOff = newTicks - check;
        if (msecOff < -100 || msecOff > 100)
        {
            // We must keep the timer running forward :)
            LONGLONG adjust = std::min(msecOff * mFrequency.QuadPart / 1000, newTime - mLast);
            mZeroTime.QuadPart += adjust;
            newTime -= adjust;
        }

        // Record last time for adjust
        mLast = newTime;

        return newTime * 1000 / mFrequency.QuadPart;
    }

    double Timer::getMicroseconds()
    {
        LARGE_INTEGER now;
        HANDLE thread = GetCurrentThread();
        // Set affinity to the first core
        DWORD_PTR oldMask = SetThreadAffinityMask(thread, mMask);
        // Query the timer
        QueryPerformanceCounter(&now);
        // Reset affinity
        SetThreadAffinityMask(thread, oldMask);
        double newTime = double(now.QuadPart - mZeroTime.QuadPart);
        // Scale by 1000 for milliseconds
        double newTicks = newTime * 1000 / mFrequency.QuadPart;
        // Detect and compensate for performance counter leaps
        // (Surprisingly common, see Microsoft KB: Q274323)
        double check = double(GetTickCount() - mStartTick);
        double msecOff = newTicks - check;
        if (msecOff < -100 || msecOff > 100)
        {
            // We must keep the timer running forward :)
            LONGLONG adjust = std::min(msecOff * mFrequency.QuadPart / 1000, newTime - mLast);
            mZeroTime.QuadPart += adjust;
            newTime -= adjust;
        }

        // Record last time for adjust
        mLast = newTime;

        return newTime * 1000000 / mFrequency.QuadPart;
    }
#else
    void Timer::reset()
    {
        gettimeofday(&mZeroTime, NULL);
        mLastTime = 0;
        mNewTime = 0;

        mZeroClock = clock();
        mLastClock = 0;
        mNewClock = 0;
    }

    double Timer::getSeconds()
    {
        TimeValue now;
        gettimeofday(&now, NULL);
        return double(now.tv_sec - mZeroTime.tv_sec) + double(now.tv_usec - mZeroTime.tv_usec) * 0.000001;
    }

    double Timer::getMilliseconds()
    {
        TimeValue now;
        gettimeofday(&now, NULL);
        return double(now.tv_sec - mZeroTime.tv_sec) * 1000 + double(now.tv_usec - mZeroTime.tv_usec) * 0.001;
    }

    double Timer::getMicroseconds()
    {
        TimeValue now;
        gettimeofday(&now, NULL);
        return double(now.tv_sec - mZeroTime.tv_sec) * 1000000 + double(now.tv_usec - mZeroTime.tv_usec);
    }
#endif

    double Timer::getSecondsCPU()
    {
        clock_t newClock = clock();
        return double(newClock - mZeroClock) / double(CLOCKS_PER_SEC);
    }

    Timer::~Timer()
    {
    }

    std::string Timer::GetTimeString()
    {
        time_t rawTime;
        time(&rawTime);
        std::string timeStr(ctime(&rawTime));
        if (timeStr.length())
        {
            timeStr[timeStr.length() - 1] = '\0';
        }
        return timeStr;
    }

    std::string Timer::GetTimeString(const std::string &format)
    {
        time_t rawTime;
        time(&rawTime);
        TimeInfo *timeInfo = localtime(&rawTime);
        char timeStr[0xFF];
        strftime(timeStr, 0xFF, format.c_str(), timeInfo);
        return std::string(timeStr);
    }

    std::string Timer::GetMillisecondsString()
    {
#if NE_PLATFORM == NE_PLATFORM_WINDOWS
        SYSTEMTIME now;
        GetSystemTime(&now);
        uint32_t ms = uint32_t(now.wMilliseconds);
#else
        TimeValue now;
        gettimeofday(&now, NULL);
        uint32_t ms = uint32_t(now.tv_usec / 1000);
#endif
        char timeStr[3];
        sprintf(timeStr, "%03u", ms);
        return std::string(timeStr);
    }

    const TimeInfo* Timer::GetTimeInfo()
    {
        time_t rawTime;
        time(&rawTime);
        return localtime(&rawTime);
    }
}
